/*
 * marketsim-eclipsetrader
 *
 * Copyright (C) 2008-2011 Steve Phelps and John Cartlidge
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 */
/*
 *  (C) Victria Ltd. 2007
 */
package victria.marketsim.eclipsetrader.trading;

import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.remoting.RemoteAccessException;

import victria.marketsim.core.MarketService;
import victria.marketsim.core.MarketState;
import victria.marketsim.core.NullOrder;
import victria.marketsim.eclipsetrader.AbstractController;
import victria.marketsim.eclipsetrader.event.MarketModelChangedEvent;
import victria.marketsim.eclipsetrader.event.VisualisationEvent;
import victria.marketsim.eclipsetrader.event.VisualisationEventListener;

import net.sourceforge.eclipsetrader.core.CorePlugin;
import net.sourceforge.eclipsetrader.core.ITradingProvider;
import net.sourceforge.eclipsetrader.core.db.Order;
import net.sourceforge.eclipsetrader.core.db.OrderSide;
import net.sourceforge.eclipsetrader.core.db.OrderStatus;
import net.sourceforge.eclipsetrader.core.db.OrderType;


/**
 * Maintains a mapping between eclipsetrader orders and duvacy orders and
 * fires a OrderSentEvent and TradingPositionChangedEvent.  
 * 
 * The OrderManager also listens to the MarketModel so that it can update
 * any matched orders in the eclipsetrader views, and track the user's
 * outstanding balance in the market.
 * 
 * @author sphelps
 *
 */
public class OrderController extends AbstractController implements
		VisualisationEventListener {

	/**
	 * The ID of our current victria.uui.core.Order in the marketplace.
	 */
	protected volatile Long currentOrderId = null;

	/**
	 * Our current eclipsetrader order.
	 */
	protected Order currentOrder;
	
	/**
	 * The order to use when there is no outstanding balance in the market
	 */
	protected NullOrder nullOrder = new NullOrder();

	/**
	 * Our current outstanding order in the market. 
	 */
	protected victria.marketsim.core.Order outstandingOrder = new NullOrder();
	
	/**
	 * The stub for the server.
	 */
	protected MarketService marketService;
	

	static Logger logger = Logger.getLogger(OrderController.class);

	/**
	 * The default trader ID to use when placing orders.
	 */
	protected Object defaultTrader;
	
	protected ITradingProvider provider;
	
	/**
	 * If true then do not update current position in response to
	 * fills.
	 */
	protected volatile boolean visualisingOnlyMode = false;
	
	
	public OrderController() {		
	}

	public void sendNew(Order order) {
//		order.setProvider(provider);
		victria.marketsim.core.Order simOrder = constructSimOrder(order);
		submit(order, simOrder);
	}
	
//	protected void sendNew(victria.marketsim.core.Order simOrder) {
//		Order order = constructEclipseTraderOrder(simOrder);
//		submit(order, simOrder);
//	}
	
	protected void notifyChangedPosition(victria.marketsim.core.Order newPosition) {
		if (!visualisingOnlyMode) {
//			fireEvent(new TradingPositionChangedEvent(newPosition));
		}
	}
	
	public synchronized void sendCancelRequest(Order order) {
		logger.debug("Cancelling order " + order);
		boolean success = marketService.cancelOrders(getDefaultTrader());
		logger.debug("success = " + success);
		if (success) {
			currentOrderId = null;
			order.setStatus(OrderStatus.CANCELED);
			order.setChanged();
			order.notifyObservers();
			outstandingOrder = nullOrder;
			notifyChangedPosition(outstandingOrder);
		}
	}

	
	
	public void cancel(Order order) {
		order.setStatus(OrderStatus.CANCELED);
	}
		
	public synchronized void submit(Order order, victria.marketsim.core.Order simOrder) {
		try {
			visualisingOnlyMode = false;
			logger.debug("current order = " + currentOrder);
			Order cancelledOrder = null;
			if (currentOrder != null) {
				logger.debug("Cancelling existing order..");
				cancelledOrder = currentOrder;
				cancel(cancelledOrder);	
				logger.debug("done.");
			}
			currentOrder = order;
			outstandingOrder = simOrder;
			logger.debug("Now current order = " + currentOrder);
			CorePlugin.getRepository().save(order);
			currentOrder.setStatus(OrderStatus.NEW);
			if (currentOrderId == null) {
				logger.debug("Placing new order..");
				currentOrderId = marketService.placeOrder(simOrder);
				logger.debug("Done.  currentOrderId = " + currentOrderId);
			} else {
				logger.debug("Replacing order " + currentOrderId + "..");
				currentOrderId = 
					marketService.replaceOrder(currentOrderId, simOrder);
				logger.debug("done.  currentOrderId = " + currentOrderId);
			}
			currentOrder.setId(currentOrderId.intValue());
			currentOrder.notifyObservers();
//			fireEvent(new OrderSentEvent(duvacyOrder));
			// Create new position provided order has not already been
			//  fulfilled (by another thread).
			if (currentOrder.getStatus() == OrderStatus.NEW) {			
				notifyChangedPosition(simOrder);
			}
			if (cancelledOrder != null) {
				cancelledOrder.notifyObservers();
			}
		} catch (RemoteAccessException e) {
			logger.error(e);
		}
	}
	
	public Order constructEclipseTraderOrder(victria.marketsim.core.Order simOrder) {
		Order result = new Order((int) simOrder.getId());
		result.setPrice(simOrder.getPrice());
		result.setQuantity(simOrder.getQuantity());
		if (simOrder.isAsk()) {
			result.setSide(OrderSide.SELL);
		} else {
			result.setSide(OrderSide.BUY);
		}
		result.setType(OrderType.LIMIT);
		result.setProvider(provider);
		logger.debug("order = " + result);
		logger.debug("provider = " + result.getProvider());
		return result;
	}
	
	public victria.marketsim.core.Order constructSimOrder(Order order) {
		victria.marketsim.core.Order simOrder = new victria.marketsim.core.Order();
		simOrder.setPrice((float) order.getPrice());
		simOrder.setQuantity(order.getQuantity());
		simOrder.setVisible(false);
		simOrder.setTrader(getTrader(order));
		if (order.getSide() == OrderSide.BUY) {
			simOrder.setDirection(victria.marketsim.core.Order.Direction.BUY);
		} else {
			simOrder.setDirection(victria.marketsim.core.Order.Direction.SELL);
		}
		return simOrder;
	}
	
	public synchronized void eventOccurred(VisualisationEvent e) {
		
		if (e instanceof MarketModelChangedEvent) {
			
			MarketModelChangedEvent event = (MarketModelChangedEvent) e;
			MarketState marketState = 
				event.getMarketStateController().getMarketState();

			logger.debug("Checking to see if current order id "
					+ currentOrderId + " is matched..");

			// If our current order is no longer in the marketplace
			//  it must have been matched			
			if (currentOrderId != null
					&& !marketState.isOutstanding(currentOrderId)) {
				
				// Check to see if we have outstanding orders
				//  that are a result of partial matches against
				//  our original order.
				List<victria.marketsim.core.Order> outstandingOrders = marketState
						.getOrders(getTrader(currentOrder));
				if (outstandingOrders != null && outstandingOrders.size() > 0) {
					// We should only have one order so get first
					outstandingOrder = 
						outstandingOrders.get(0);
					logger.debug("Remaining order = " + outstandingOrder);
					currentOrderId = outstandingOrder.getId();
					currentOrder.setStatus(OrderStatus.PARTIAL);
					currentOrder.setId(currentOrderId.intValue());
					int filledQuantity = currentOrder.getQuantity()
											- outstandingOrder.getQuantity();
					currentOrder.setFilledQuantity(filledQuantity);
					// New trading position is remaining balance
					notifyChangedPosition(outstandingOrder);
//					fireEvent(new OrderFulfilledEvent(currentOrderId,
//														filledQuantity));
				} else {
					// We have no orders left in the market so our 
					//  original order must have been completely matched.
					logger.debug("Order fulfilled");
					currentOrder.setStatus(OrderStatus.FILLED);
					currentOrder.setFilledQuantity(currentOrder.getQuantity());					
//					fireEvent(new OrderFulfilledEvent(currentOrderId,
//												  currentOrder.getQuantity()));
					// No trading position left so switch to nullOrder
					outstandingOrder = nullOrder;
					notifyChangedPosition(outstandingOrder);
					// No order left to track
					currentOrderId = null;
				}
				// Update eclipsetrader Orders view
				logger.debug("Updating " + currentOrder + "..");
				currentOrder.setChanged();
				currentOrder.notifyObservers();
				logger.debug("done.");
				
			}
			logger.debug("done.");
		}
	}

	public Object getTrader(Order order) {
//		if (order.getAccount() != null
//				&& order.getAccount().getDescription() != null) {
//			return order.getAccount().getDescription();
//		} else {
			return getDefaultTrader();
//		}
	}

	public Order getCurrentOrder() {
		return currentOrder;
	}

	protected void setCurrentOrder(Order currentOrder) {
		this.currentOrder = currentOrder;
	}

	public Long getCurrentOrderId() {
		return currentOrderId;
	}

	protected void setCurrentOrderId(Long currentOrderId) {
		this.currentOrderId = currentOrderId;
	}

	public MarketService getMarketService() {
		return marketService;
	}

	public void setMarketService(MarketService marketService) {
		this.marketService = marketService;
	}
	
	
	public ITradingProvider getProvider() {
		return provider;
	}

	public void setProvider(ITradingProvider provider) {
		this.provider = provider;
	}

	public synchronized Object getDefaultTrader() {
		if (defaultTrader == null) {
			try {
				defaultTrader = marketService.allocateUniqueTrader();
			} catch (RemoteAccessException e) {
				// Server is probably not up yet - don't sweat
				logger.warn(e);
			}
		}
		return defaultTrader;
	}

}
